package webgamemaker.RPGMakerVXLoader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * classe de decodage Marshal (ruby)
 * portage de http://www.ruby-doc.org/doxygen/1.8.4/marshal_8c-source.html
 */
public class Marshal 
{
	final static int MAJOR = 4;
	final static int MINOR = 8;
	
	final static char TYPE_NIL =       '0';
	final static char TYPE_TRUE =      'T';
	final static char TYPE_FALSE =     'F';
	final static char TYPE_FIXNUM =    'i';
	final static char TYPE_EXTENDED =  'e';
	final static char TYPE_UCLASS =    'C';
	final static char TYPE_OBJECT =    'o';
	final static char TYPE_DATA =      'd';
	final static char TYPE_USERDEF =   'u';
	final static char TYPE_USRMARSHAL ='U';
	final static char TYPE_FLOAT =     'f';
	final static char TYPE_BIGNUM =    'l';
	final static char TYPE_STRING =    '"';
	final static char TYPE_REGEXP =    '/';
	final static char TYPE_ARRAY =     '[';
	final static char TYPE_HASH =      '{';
	final static char TYPE_HASH_DEF =  '}';
	final static char TYPE_STRUCT =    'S';
	final static char TYPE_MODULE_OLD ='M';
	final static char TYPE_CLASS =     'c';
	final static char TYPE_MODULE =    'm';
	final static char TYPE_SYMBOL =    ':';
	final static char TYPE_SYMLINK =   ';';
	final static char TYPE_IVAR =      'I';
	final static char TYPE_LINK =      '@';

	public static long  r_long(TabOffset data) throws MarshalException
	{
		long x;
		int c = (int)((data.readChar()^128)-128);
		System.out.println(c);
		long i;
		if (c == 0) 
			return 0;
		if (c > 0) 
		{
			if (4 < c && c < 128) {
				return c - 5;
			}
			if (c > 4/*sizeof(long)*/) 
				throw new MarshalException("nombre trop grand pour un long");
			x = 0;
			for (i=0;i<c;i++) {
				long temp = ((long)data.readChar()) & 255;
				long temp2 = (long)temp << (8*i);
				x |= temp2;
				System.out.println(temp+" "+temp2);
			}
		}
		else {
			if (-129 < c && c < -4) {
				return c + 5;
			}
			c = -c;
			if (c > 4/*sizeof(long)*/) 
				throw new MarshalException("nombre trop grand pour un long");
			x = -1;
			for (i=0;i<c;i++) {
				x &= ~((long)0xff << (8*i));
				x |= (((long)data.readChar())&255) << (8*i);
			}
		}
		return x;
	}

	public static String r_string(TabOffset data) throws MarshalException
	{
		long size = r_long(data);
		System.out.println(size);
		char temp[] = new char[(int)size];
		for(int i=0;i<size;i++)
			temp[i] = (char)data.readChar();
		return new String(temp);
	}
	
	public static String r_symbol(TabOffset data) throws MarshalException
	{
		if(data.readChar() == TYPE_SYMLINK)
		{
			long id = r_long(data);
			return data.listSymbol.get((int)id);
			//throw new MarshalException("type non supporte");
		}
		else 
		{
			
			String symbol = r_string(data);
			data.addSymbol(symbol);
			return symbol;
		}
	}
	
	public static void r_ivar(MarshalArrayStringValue obj, TabOffset data) throws MarshalException
	{
		long len = r_long(data);
		while(len>0)
		{
			String name = r_symbol(data);
			System.out.println("name : "+name);
			obj.add_value(name,r_object0(data,null));
			len--;
		}
	}
	
	public static MarshalValue r_object0(TabOffset data, IntPtr ivp) throws MarshalException
	{
		MarshalValue val = new MarshalValue(TYPE_NIL);
		byte type;
		switch(type = data.readChar())
		{
		case TYPE_IVAR:
			IntPtr ivar = new IntPtr();
			ivar.val = 1;
			val = r_object0(data,ivar);
			if(ivar.val > 0)
				r_ivar((MarshalArrayStringValue)val,data);
			break;
		case TYPE_NIL:
			val = new MarshalValue(TYPE_NIL);
			break;
		case TYPE_TRUE:
			val = new MarshalValue(TYPE_TRUE);
			break;
		case TYPE_FALSE:
			val = new MarshalValue(TYPE_FALSE);
			break;
		case TYPE_FIXNUM:
			val = new MarshalValueFixNum(r_long(data));
			break;
		case TYPE_STRING:
			val = new MarshalValueString(r_string(data));
			break;
		case TYPE_ARRAY:
		{
			val = new MarshalValueArray();
			long len = r_long(data);
			while(len > 0)
			{
				((MarshalValueArray)val).add_value(r_object0(data, null));
				len--;
			}
			break;
		}
		case TYPE_HASH:
		{
			val = new MarshalValueHash(TYPE_HASH);
			long num = r_long(data);
			System.out.println("hash : "+num+" item");
			while(num>0)
			{
				((MarshalValueHash)val).add_value(r_object0(data, null),r_object0(data, null));
				num--;
			}
			break;
		}
		case TYPE_USERDEF:
		{
			String classe = r_symbol(data);
			System.out.println("classe : "+classe);
			val = new MarshalValueUserDef(classe);
			long size = r_long(data);
			MarshalValueUserDef temp = (MarshalValueUserDef)val;
			while(size>0)
			{
				temp.add_value(data.readChar());
				size--;
			}
			if(ivp != null)
			{
				r_ivar(temp, data);
				ivp.val = 0;
			}
			break;
		}
		case TYPE_OBJECT:
		{
			String symbole = r_symbol(data);
			System.out.println("name : "+symbole);
			val = new MarshalValueObject(symbole);
			r_ivar((MarshalValueObject)val,data);
			break;
		}
		default:
			throw new MarshalException("type non supporte : "+type);
		}
		return val;
	}
	public static MarshalValue decode(byte data[]) throws MarshalException
	{
		if(data.length < 2)
			throw new MarshalException("fichier invalide");
		if(data[0] != MAJOR || data[1] < MINOR)
			throw new MarshalException("mauvaise version");
		System.out.println("version valide");
		return r_object0(new TabOffset(data,2), null);
	}
	
	public static class IntPtr
	{
		public int val;
	}
	
	public static class TabOffset
	{
		public byte tab[];
		public int offset;
		public ArrayList<String> listSymbol;
		public TabOffset(byte tab[], int offset)
		{
			this.tab = tab;
			this.offset = offset;
			listSymbol = new ArrayList<String>();
		}
		public TabOffset(byte tab[])
		{
			this(tab,0);
		}
		public byte readChar()
		{
			return tab[offset++];
		}
		public void addSymbol(String symbol)
		{
			listSymbol.add(symbol);
		}
	}
	
	public static class MarshalException extends Exception
	{
		public MarshalException(String message)
		{
			super(message);
		}
	}
	
	public static class MarshalValue
	{
		public char type;
		
		public MarshalValue(char type)
		{
			this.type = type;
		}
	}
	
	public static class MarshalValueHash extends MarshalValue
	{
		public Map<MarshalValue, MarshalValue> value;
		
		public MarshalValueHash(char type)
		{
			super(type);
			value = new HashMap<MarshalValue, MarshalValue>();
		}
		
		public void add_value(MarshalValue key, MarshalValue val)
		{
			value.put(key, val);
		}
	}
	
	public static class MarshalValueFixNum extends MarshalValue
	{
		public long data;
		
		public MarshalValueFixNum(long data)
		{
			super(TYPE_FIXNUM);
			this.data = data;
		}
	}
	
	public static class MarshalValueString extends MarshalValue
	{
		public String data;
		
		public MarshalValueString(String data)
		{
			super(TYPE_STRING);
			this.data = data;
		}
	}
	
	public static class MarshalValueArray extends MarshalValue
	{
		public String name;
		public ArrayList<MarshalValue> value;
		
		public MarshalValueArray()
		{
			super(TYPE_ARRAY);
			this.value = new ArrayList<MarshalValue>();
		}
		
		public void add_value(MarshalValue val)
		{
			value.add(val);
		}
	}
	
	public interface MarshalArrayStringValue
	{
		public void add_value(String name, MarshalValue val);
		public MarshalValue get_value(String name);
	}
	
	public static class MarshalValueUserDef extends MarshalValue implements MarshalArrayStringValue
	{
		public String name;
		public ArrayList<Byte> value;
		public Map<String, MarshalValue> value2;
		
		public MarshalValueUserDef(String name)
		{
			super(TYPE_USERDEF);
			this.name = name;
			this.value = new ArrayList<Byte>();
			this.value2 = new HashMap<String, MarshalValue>();
		}
		
		public void add_value(byte val)
		{
			value.add(val);
		}
		
		public void add_value(String name, MarshalValue val)
		{
			value2.put(name, val);
		}
		
		public Byte get_value(int id)
		{
			return value.get(id);
		}
		
		public MarshalValue get_value(String name)
		{
			return value2.get(name);
		}
	}
	
	public static class MarshalValueObject extends MarshalValue implements MarshalArrayStringValue
	{
		public String name;
		public Map<String, MarshalValue> value;
		
		public MarshalValueObject(String name)
		{
			super(TYPE_OBJECT);
			this.name = name;
			this.value = new HashMap<String, MarshalValue>();
		}
		
		public void add_value(String name, MarshalValue val)
		{
			value.put(name, val);
		}
		
		public MarshalValue get_value(String name)
		{
			return value.get(name);
		}
	}

}
